Set a site's security level in site.json

Clarification By design
The "security" section of a site's settings (#/security.json, or "security" in #/site.json, inherited from the app) decides which visitors must pass a Cloudflare Turnstile check before the site answers: level normal (nobody), elevated (probes, flagged IPs and low-trust countries), high (everyone except high-trust countries) or underAttack (everyone). The check runs before the page cache. From high upwards search engines are stopped too, so set "until" when you raise the level, and "exclude" paths that machines call, such as webhooks.
Applies to: site.jsonsecurity.jsonSecurityPublished sites

What you'll see

A site is being scraped, flooded or probed, and you want visitors to prove they are human before they get anything — or a site suddenly shows a “Checking your browser” page to some visitors, and a script or webhook that calls it gets 403.

What's actually happening

The level is set per site, in #/security.json or in the security section of #/site.json. A site built on an app inherits the app's security settings key by key and only needs what differs (see Configure site settings and inherit them from the app). In #/site.json:

"security": {
  "level": "elevated",
  "clearanceMinutes": 60,
  "until": "2026-11-01T12:00",
  "exclude": [ "/api/webhooks/*", "/health" ],
  "abuseBlocking": true
}
levelWho must pass the checkSearch engines
normalNobody. The firewall and automatic blocking work as always.indexed
elevatedRequests for flagged URLs that the site does not have (probes such as wp-login.php), IP addresses that are already flagged, and countries with low trust.indexed
highEveryone except visitors from countries with high trust.stopped
underAttackEveryone.stopped
  • What the visitor gets. A page view (a GET or HEAD navigation) gets a challenge page with status 403. Everything else — API calls, fetch, images, files, POST — gets 403 with {"ResultCode":-213,"Message":"Verification required."}. Both are no-store.
  • A passed check lasts clearanceMinutes (default 60, at most one week) for that site, in a cookie.
  • The check runs before the cache, so cached pages are protected too.
  • until (ISO 8601; without a time zone, the server's local time) ends the site's own level. Afterwards the platform default applies. A value that cannot be read is ignored, and the level then has no end.
  • exclude lists paths, relative to the site, that never get the check. A trailing * means everything below; otherwise the path must match exactly. Case does not matter. A site's exclude replaces the app's list as a whole, so repeat the app's paths the site still needs.
  • The platform default applies when neither the site nor its app sets a level. It is normal or elevated, never higher. The site's own level always wins, over the app's and the platform's, including "normal", which is how a site opts out.
  • An unknown level (a typo) counts as not set.
  • abuseBlocking (default true): false lets visitors through whose address is blocked only because the IP data provider marks it as abusive. All other firewall rules still apply.

What to do

  • Use elevated as the everyday level for a public site. It challenges only what already looks suspicious, and search engines are not affected.
  • Raise to high or underAttack only during an attack, and always with until. A forgotten underAttack keeps search engines out for as long as it stands.
  • Put every path that a machine calls in exclude before you raise the level: webhooks, payment callbacks, uptime checks. They cannot pass Turnstile.
  • Client code that calls your API should treat 403 with ResultCode -213 as “reload the page”: the page view gets the challenge, and the clearance cookie then covers the API calls.
  • Verify with curl -I from a machine without the clearance cookie: an excluded path must not answer 403.